package com.qipay.pay.impl;

import com.qipay.baas.model.CpChannel;
import com.qipay.baas.model.Order;
import com.qipay.baas.model.Sp;
import com.qipay.core.help.DicHelper;
import com.qipay.cp.CpService;
import com.qipay.cpchannel.service.CpChannelService;
import com.qipay.order.config.OrderSettleStateEnum;
import com.qipay.order.config.OrderStateEnum;
import com.qipay.order.service.OrderService;
import com.qipay.pay.AlipayService;
import com.qipay.pay.entity.CpPayAlipayNativeRequest;
import com.qipay.pay.entity.CpPayAlipayTradeCloseRequest;
import com.qipay.pay.entity.CpPayAlipayTradeQueryRequest;
import com.qipay.scalar.type.ApiResultCodeEnum;
import com.qipay.scalar.type.ChannelTypeEnum;
import com.qipay.security.jwt.JWTUser;
import com.qipay.security.sign.service.SignService;
import com.qipay.sp.*;
import com.qipay.sp.excutor.ExcutorFactory;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.util.*;

@Service
public class AlipayServiceImpl implements AlipayService {

    protected final Log logger = LogFactory.getLog(this.getClass());

    @Autowired
    OrderService orderService;

    @Autowired
    CpChannelService cpChannelService;

    @Autowired
    SpService spService;
    @Autowired
    SignService signService;
    @Autowired
    SpConfigService spConfigService;
    @Autowired
    CpSpChannelService cpSpChannelService;


    @Transactional
    @Override
    public Map<String, Object> payNative(
            CpPayAlipayNativeRequest entity,
            JWTUser loggedUser) throws Exception {

        int decrease = orderService.randomDecrease(DicHelper.getCodeByName("randomDecrease","随机立减最大值").getDictValue());
        Assert.notNull(loggedUser, "找不到登录信息");
        Assert.notNull(entity, "请输入交易信息");
        Assert.notNull(entity.getBody(), "请输入商品描述");
        Assert.notNull(entity.getMchId(), "请输入商户号");
        Assert.isTrue(StringUtils.equals(entity.getMchId(), loggedUser.getUsername())
                , "输入的商户号与登录信息不一致");
        Assert.notNull(entity.getOutTradeNo(), "请输入商户订单号");
        Assert.notNull(entity.getTotalFee(), "请输入总金额");
        Assert.isTrue(entity.getTotalFee() > (10+decrease), "金额太低");
        Assert.notNull(entity.getMchCreateIp(), "请输入订单生成的机器 IP");
        Assert.notNull(entity.getNotifyUrl(), "请输入notify_url");
        Assert.notNull(entity.getNonceStr(), "请输入nonce_str");

        CpChannel cpChannel = cpChannelService.getByCpIdAndChannelType(loggedUser.getId(),
                ChannelTypeEnum.ALIPAY_SCAN.getType());
        Assert.notNull(cpChannel, "该商户没有开通支付宝扫码服务");
        Assert.isTrue(cpChannel.getState() == 0, "该商户的支付宝扫码服务已经被禁用");
        Assert.notNull(cpChannel.getFeeRecharge(), "该商户没有配置费率");
        Assert.notNull(cpChannel.getRateAdvince(), "该商户没有配置到账比率");

        Order order = orderService.getByCpidAndOutTradeNo(loggedUser.getId(), entity.getOutTradeNo());
        if (order != null) {
            Assert.isTrue(Objects.equals(OrderStateEnum.NOTPAY.getType(), order.getState()),
                    "无效的订单状态:" + OrderStateEnum.getByType(order.getState()).getInfo());
        } else {
            order = new Order();
            order.setCpId(loggedUser.getId());
            order.setChannelType(ChannelTypeEnum.ALIPAY_SCAN.getType());
            order.setBody(entity.getBody());
            order.setAmount(entity.getTotalFee());
            order.setFeeRecharge(cpChannel.getFeeRecharge());
            order.setState(OrderStateEnum.NOTPAY.getType());
            order.setTradeNo(orderService.createOrder(18));
            order.setOutTradeNo(entity.getOutTradeNo());
            order.setNotifyUrl(entity.getNotifyUrl());
            order.setAttach(entity.getAttach());
            order.setCreateTime(new Date());
            order.setActualPayAmount(order.getAmount()-decrease);
            order.setSettleStatus(OrderSettleStateEnum.NOTSETTLE.getType());
            order.setRemark(entity.getMchCreateIp());

            order.setFee(cpChannel.getFeeRecharge().multiply(
                    new BigDecimal(entity.getTotalFee().toString())).intValue());
            order.setActualAmount(order.getAmount() - order.getFee());
            order.setAdvanceAmount(cpChannel.getRateAdvince().multiply(
                    new BigDecimal(order.getActualAmount().toString())).intValue());
            order.setDisableAmount(order.getActualAmount() - order.getAdvanceAmount());

            CpChannel agentChannel = cpChannelService.getAgentChannel(cpChannel);
            if(agentChannel != null) {
                order.setAgentId(agentChannel.getCpId());
                order.setAgentFeeRecharge(agentChannel.getFeeRecharge());
                order.setAgentFee(agentChannel.getFeeRecharge().multiply(
                        new BigDecimal(entity.getTotalFee().toString())
                ).intValue());
            }
            List<Long> spIds = null;
            if(!loggedUser.getPassword().contains("test:1000003")){
                //非测试，考虑通道优先~，金额是否支持来设计
                if((order.getActualPayAmount()<10000&&order.getActualPayAmount()%100==0)||
                        (order.getActualPayAmount()<100000&&order.getActualPayAmount()%1000==0)||
                        (order.getActualPayAmount()<500000&&order.getActualPayAmount()%10000==0)){
                    spIds = cpSpChannelService.selectSpIdsFirst(loggedUser.getId().intValue(),ChannelTypeEnum.ALIPAY_SCAN.getType(),1);
                    if(spIds==null||spIds.size()==0){
                        spIds = cpSpChannelService.selectSpIdsFirst(loggedUser.getId().intValue(),ChannelTypeEnum.ALIPAY_SCAN.getType(),0);
                    }
                }else{//必须找支持小数，且按优先级排序
                    spIds = cpSpChannelService.selectSpIdsFirstDouble(loggedUser.getId().intValue(),ChannelTypeEnum.ALIPAY_SCAN.getType(),1,1);
                    if(spIds==null||spIds.size()==0){
                        spIds = cpSpChannelService.selectSpIdsFirstDouble(loggedUser.getId().intValue(),ChannelTypeEnum.ALIPAY_SCAN.getType(),1,0);
                    }
                }

            }else{
                spIds = new ArrayList<>();spIds.add(Long.valueOf(loggedUser.getPassword().split(":")[2]));
            }
            Assert.isTrue(spIds.size()>0,"找不到可用的通道:未指定通道");
            order = orderService.selectASp(order, ChannelTypeEnum.ALIPAY_SCAN,spIds);

            order = orderService.insert(order);
            if(order.getId() == null || order.getId() == 0) {
                logger.error("订单信息无法入库，order.id:" + order.getId());
                throw new Exception("无法生成订单");
            }
        }

        Map<String, Object> result = excuteResult(order,loggedUser.getSecret());
        //考虑到有的通道会死掉的情况，所以这里判断下降低通道死亡对成功率的影响
        if(result == null||"1".equals(result.get("result_code").toString())) {
            Sp sp = spService.get(order.getSpId().intValue());
            //sp.setState((byte)1);自动关闭的先关掉，后面再改进，可以改成预警次数~加一个接口请求日志，既可以预警，也可以查看原因
            sp.setFailReason("支付异常："+(result==null?"检查参数":result.get("message")));
            sp.setUpdateDate(new Date());
            spService.update(sp);
            order = orderService.selectASp(order, ChannelTypeEnum.ALIPAY_SCAN);
            if(orderService.modify(order)) {
                result = excuteResult(order,loggedUser.getSecret());
            }
        }

        return result;
    }

    /**
     * 处理创建订单之后的执行器
     * @param order
     * @param secret
     * @return
     * @throws Exception
     */
    private Map<String, Object> excuteResult(Order order,String secret ) throws Exception {
        Map<String, Object> result = new HashMap<>();
        String isRaw = spConfigService.getValue(order.getSpId(),"is_raw");
        if("1".equals(isRaw)) {//等于1，需要重定向form页面的
            String mchId = order.getCpId().toString();
            String tradeNo = order.getTradeNo();
            String nonceStr = RandomStringUtils.randomNumeric(32);

            Map<String, Object> params = new HashMap<>();
            params.put("mch_id", mchId);
            params.put("trade_no", tradeNo);
            params.put("nonce_str", nonceStr);
            params.put("type", "native");
            String mySig = signService.getSign(params, secret, "sign");
            params.put("sign", mySig);

            String redirectUrl = spConfigService.getValue(order.getSpId(), "redirect_url");
            Assert.notNull(redirectUrl, "通道未配置redirect_url");
            redirectUrl += "?mch_id=" + mchId + "&trade_no=" + tradeNo + "&nonce_str=" + nonceStr+"&type=native"
                    + "&sign=" + mySig;
            result.put("code_img_url", redirectUrl);
            result.put("result_code", "0");
        }else{
            result = excuteFactoryExcutor(order);
        }
        return result;
    }

    @Override
    public Map<String, Object> nativePay(Long orderId) throws Exception {
        Assert.notNull(orderId, "请输入交易信息");
        Order order = orderService.get(orderId);
        Assert.notNull(order, "请输入交易信息");
        Assert.isTrue(Objects.equals(OrderStateEnum.NOTPAY.getType(), order.getState()),
                "无效的订单状态:" + OrderStateEnum.getByType(order.getState()).getInfo());
        Map<String, Object> result = excuteFactoryExcutor(order);
        return result;
    }

    public Map<String, Object> excuteFactoryExcutor(Order order) throws Exception {
        Map<String, Object> result = null;
        try {
            ExcutorFactory excutorFactory = spService.getFactory(order.getSpId());
            result = excutorFactory.createAlipayNativeExcutor().excutor(order);
        }catch (Exception ex) {
            ex.printStackTrace();
            logger.error(order.getTradeNo() + ":" + order.getChannelType().toString()
                    + " -- 执行工厂方法出错:" + ex.toString());
        }
        return result;
    }

    @Transactional
    @Override
    public Map<String, Object> tradeClose(
            CpPayAlipayTradeCloseRequest entity,
            JWTUser loggedUser
    ) throws Exception {
        Assert.notNull(loggedUser, "找不到登录信息");
        Assert.notNull(entity, "请输入交易信息");
        Assert.notNull(entity.getMchId(), "请输入商户号");
        Assert.isTrue(StringUtils.equals(entity.getMchId(), loggedUser.getUsername())
                , "输入的商户号与登录信息不一致");
        Assert.isTrue(entity.getOutTradeNo() !=null || entity.getTransactionId() != null,
                "请输入CP订单号或琦付订单号");
        Assert.notNull(entity.getNonceStr(), "请输入nonce_str");

        Order order = null;
        if(StringUtils.isNotBlank(entity.getTransactionId())) {
            order = orderService.getByCpidAndTradeNo(loggedUser.getId(), entity.getTransactionId());
        } else if(StringUtils.isNotBlank(entity.getOutTradeNo())) {
            order = orderService.getByCpidAndOutTradeNo(loggedUser.getId(), entity.getOutTradeNo());
        }

        Assert.notNull(order, "找不到相应订单信息");

        if(Objects.equals(order.getState(), OrderStateEnum.FREEZED.getType())
                || Objects.equals(order.getState(), OrderStateEnum.SUCCESS.getType())
                || Objects.equals(order.getState(), OrderStateEnum.CLOSE.getType())) {
            Map<String, Object> result = new HashMap<>();
            result.put("result_code", 1);
            result.put("mch_id", loggedUser.getUsername());
            result.put("nonce_str", System.currentTimeMillis());
            result.put("err_code", ApiResultCodeEnum.ERROR_ORDER_STATUS.getInfo());
            result.put("err_msg", "当前订单状态为" + OrderStateEnum.getByType(order.getState()).toString());
            return result;
        }

        ExcutorFactory excutorFactory = spService.getFactory(order.getSpId());
        Map<String, Object> result = excutorFactory.createAlipayTradeCloseExcutor().excutor(order);
        Assert.notNull(result, "请求第三方接口失败");
        if(result.get("result_code") != null && "0".equals(result.get("result_code").toString()))
        {
            orderService.setOrderClose(order);
        }
        return result;
    }

    @Transactional
    @Override
    public Map<String, Object> tradeQuery(
            CpPayAlipayTradeQueryRequest entity,
            JWTUser loggedUser) throws Exception {

        Assert.notNull(loggedUser, "找不到登录信息");
        Assert.notNull(entity, "请输入交易信息");
        Assert.notNull(entity.getMchId(), "请输入商户号");
        Assert.isTrue(StringUtils.equals(entity.getMchId(), loggedUser.getUsername())
                , "输入的商户号与登录信息不一致");
        Assert.isTrue(entity.getOutTradeNo() !=null || entity.getTransactionId() != null,
                "请输入CP订单号或琦付订单号");
        Assert.notNull(entity.getNonceStr(), "请输入nonce_str");

        Order order = null;
        if(StringUtils.isNotBlank(entity.getTransactionId())) {
            order = orderService.getByCpidAndTradeNo(loggedUser.getId(), entity.getTransactionId());
        } else if(StringUtils.isNotBlank(entity.getOutTradeNo())) {
            order = orderService.getByCpidAndOutTradeNo(loggedUser.getId(), entity.getOutTradeNo());
        }

        Assert.notNull(order, "找不到相应订单信息");

        if(!Objects.equals(order.getState(), OrderStateEnum.NOTPAY.getType())
                && !Objects.equals(order.getState(), OrderStateEnum.SUCCESS.getType())) {
            Map<String, Object> result = new HashMap<>();
            result.put("result_code", 0);
            result.put("mch_id", loggedUser.getUsername());
            result.put("nonce_str", System.currentTimeMillis());
            result.put("trade_state", OrderStateEnum.getByType(order.getState()).toString());
            return result;
        }

        ExcutorFactory excutorFactory = spService.getFactory(order.getSpId());
        Map<String, Object> result = excutorFactory.createAlipayTradeQueryExcutor().excutor(order);
        Assert.notNull(result, "请求第三方接口失败");

        if(result.get("result_code") != null && "0".equals(result.get("result_code").toString())
                && result.get("trade_state") != null && "SUCCESS".equals(result.get("trade_state").toString()))
        {
            if(Objects.equals(order.getState(), OrderStateEnum.NOTPAY.getType())) {
                String outTransactionId = result.getOrDefault("out_transaction_id", "").toString();
                orderService.setOrderSuccess(order.getId(), outTransactionId);
            }
            result.remove("out_transaction_id");
        }
        return result;
    }
}
